﻿using System;
using System.Collections;


namespace BizElements.BusinessLayer
{
    /// <summary>
    /// Collection of <see cref="BusinessRule"/> objects.
    /// </summary>
    [Serializable]
    public sealed class BusinessRuleCollection : IEnumerable
    {
        #region Fields.

        ArrayList list = new ArrayList();

        #endregion

        #region Contructors.

        /// <summary>
        /// Initializes a new instance of <b>BusinessRuleCollection</b> class.
        /// </summary>
        public BusinessRuleCollection()
        { }

        /// <summary>
        /// Initializes a new instance of <b>BusinessRuleCollection</b> class.
        /// </summary>
        /// <param name="firstRuleInCollection">First rule to add to collection.</param>
        public BusinessRuleCollection(BusinessRule firstRuleInCollection)
        {
            Add(firstRuleInCollection);
        }

        #endregion

        #region Indexers.

        /// <summary>
        /// Gets the element at the specified index.
        /// </summary>
        /// <param name="index">The zero-based index of the element to get.</param>
        /// <returns>The element at the specified index.</returns>
        public BusinessRule this[int index]
        {
            get { return (BusinessRule)this.list[index]; }
        }

        /// <summary>
        /// Searches the collection for the first occurance of a rule with the specified <see cref="BusinessRule.Name"/>.
        /// </summary>
        /// <param name="ruleName">Name of business rule.</param>
        /// <returns>Specified rule or <b>null</b> if a rule with the given name is not found in the collection.</returns>
        public BusinessRule this[string ruleName]
        {
            get
            {
                foreach (BusinessRule rule in this.list)
                    if (rule.Name == ruleName)
                        return rule;

                return null;
            }
        }

        #endregion

        #region Add.

        /// <summary>
        /// Adds a <b>BusinessRule</b> to the end of the <b>BusinessRuleCollection</b>.
        /// </summary>
        /// <param name="rule">The rule to be added to the end of the collection. <b>null</b> is not allowed.</param>
        /// <returns>The rule which was added to the collection.</returns>
        public BusinessRule Add(BusinessRule rule)
        {
            if (rule == null)
                throw new ArgumentException("Null is not allowed.");

            this.list.Add(rule);
            return rule;
        }

        /// <summary>
        /// Creates and adds a <b>BusinessRule</b> to the end of the <b>BusinessRuleCollection</b>.
        /// </summary>
        /// <param name="name">Name of the rule. Recommendation: the name should uniquelly identify the rule.</param>
        /// <param name="isObeyed">A value indicating whether the rule is obeyed or broken.</param>
        /// <param name="description">Friendly description of the rule for the client.</param>
        /// <param name="affectedFields">Array of business object's fields which are affected by this rule.</param>
        /// <returns>The rule which was added to the collection.</returns>
        public BusinessRule Add(string name, bool isObeyed, FormattedMessage description, params string[] affectedFields)
        {
            BusinessRule rule = new BusinessRule(name, isObeyed, description, affectedFields);
            this.list.Add(rule);
            return rule;
        }

        /// <summary>
        /// Creates and adds a <b>BusinessRule</b> to the end of the <b>BusinessRuleCollection</b>.
        /// </summary>
        /// <param name="name">Name of the rule. Recommendation: the name should uniquelly identify the rule.</param>
        /// <param name="isObeyed">A value indicating whether the rule is obeyed or broken.</param>
        /// <param name="descriptionSource"><see cref="ITextSource"/> from which a friendly description of the rule</param>
        /// <param name="affectedFields">Array of business object's fields which are affected by this rule.</param>
        /// <returns>The rule which was added to the collection.</returns>
        public BusinessRule Add(string name, bool isObeyed, ITextSource descriptionSource, params string[] affectedFields)
        {
            BusinessRule rule = new BusinessRule(name, isObeyed, descriptionSource, affectedFields);
            this.list.Add(rule);
            return rule;
        }

        /// <summary>
        /// Adds all rules in the given collection to the current <b>BusinessRuleCollection</b>.
        /// </summary>
        /// <param name="rules">Rules to be added to the end of the collection.</param>
        public void Add(BusinessRuleCollection rules)
        {
            foreach (BusinessRule rule in rules)
                Add(rule);
        }

        #endregion

        #region Properties.

        /// <summary>
        /// Gets the number of elements actually contained in the collection.
        /// </summary>
        public int Count
        {
            get { return this.list.Count; }
        }

        /// <summary>
        /// Gets the value indicating whether the collection contains broken rules.
        /// </summary>
        public bool HasBrokenRules
        {
            get
            {
                foreach (BusinessRule rule in this.list)
                    if (rule.IsBroken)
                        return true;

                return false;
            }
        }

        /// <summary>
        /// Gets the value indicating whether all rules in the collection are obeyed.
        /// </summary>
        public bool AllRulesAreObeyed
        {
            get { return !this.HasBrokenRules; }
        }

        #endregion

        #region GetBrokenRules.

        /// <summary>
        /// Searches the collection for broken rules and returns them.
        /// </summary>
        /// <returns>A collection of broken rules.</returns>
        public BusinessRuleCollection GetBrokenRules()
        {
            BusinessRuleCollection brokenRules = new BusinessRuleCollection();
            foreach (BusinessRule rule in this.list)
                if (rule.IsBroken)
                    brokenRules.Add(rule);

            return brokenRules;
        }

        #endregion

        #region GetRulesNotAssociatedWithFields.

        /// <summary>
        /// Searches the collection for all rules which do not affect specific fields.
        /// </summary>
        /// <param name="onlyBrokenRules">Indicates whether only the broken rules are to be returned.</param>
        /// <returns>A collection of rules not associated with specific fields.</returns>
        public BusinessRuleCollection GetRulesNotAssociatedWithFields(bool onlyBrokenRules)
        {
            if (onlyBrokenRules)
                return GetRulesNotAssociatedWithFields();
            else
                return GetBrokenRulesNotAssociatedWithFields();
        }

        private BusinessRuleCollection GetRulesNotAssociatedWithFields()
        {
            BusinessRuleCollection rules = new BusinessRuleCollection();
            foreach (BusinessRule rule in this.list)
                if (rule.AffectedFields.Length == 0)
                    rules.Add(rule);

            return rules;
        }

        private BusinessRuleCollection GetBrokenRulesNotAssociatedWithFields()
        {
            BusinessRuleCollection rules = new BusinessRuleCollection();
            foreach (BusinessRule rule in this.list)
                if (rule.AffectedFields.Length == 0 && rule.IsBroken)
                    rules.Add(rule);

            return rules;
        }

        #endregion

        #region GetRulesForField.

        /// <summary>
        /// Searches the collection for all rules which affect the specified field.
        /// </summary>
        /// <param name="field">Field affected by rules.</param>
        /// <param name="onlyBrokenRules">Indicates whether only the broken rules are to be returned.</param>
        /// <returns>A collection of rules associated with the specified field.</returns>
        public BusinessRuleCollection GetRulesForField(string field, bool onlyBrokenRules)
        {
            if (onlyBrokenRules)
                return GetBrokenRulesForField(field);
            else
                return GetRulesForField(field);
        }

        private BusinessRuleCollection GetRulesForField(string field)
        {
            BusinessRuleCollection rules = new BusinessRuleCollection();
            foreach (BusinessRule rule in this.list)
                if (rule.AffectsField(field))
                    rules.Add(rule);

            return rules;
        }

        private BusinessRuleCollection GetBrokenRulesForField(string field)
        {
            BusinessRuleCollection brokenRules = new BusinessRuleCollection();
            foreach (BusinessRule rule in this.list)
                if (rule.IsBroken && rule.AffectsField(field))
                    brokenRules.Add(rule);

            return brokenRules;
        }

        #endregion

        #region GetUnhandledRules.

        /// <summary>
        /// Searches the collection for rules which do not affect the specified fields.
        /// </summary>
        /// <param name="handledFields">Array of fields whose rules are to be excluded from the search.</param>
        /// <param name="onlyBrokenRules">Indicates whether only the broken rules are to be returned.</param>
        /// <returns>A collection of fields which are not associated with any of the given fields.</returns>
        public BusinessRuleCollection GetUnhandledRules(string[] handledFields, bool onlyBrokenRules)
        {
            if (onlyBrokenRules)
                return GetBrokenUnhandledRules(handledFields);
            else
                return GetAllUnhandledRules(handledFields);
        }

        private BusinessRuleCollection GetAllUnhandledRules(string[] handledFields)
        {
            BusinessRuleCollection unhandledRules = new BusinessRuleCollection();
            foreach (BusinessRule rule in this.list)
            {
                bool isHandled = false;
                foreach (string field in handledFields)
                {
                    if (rule.AffectsField(field))
                    {
                        isHandled = true;
                        break;
                    }
                }

                if (!isHandled)
                    unhandledRules.Add(rule);
            }

            return unhandledRules;
        }

        private BusinessRuleCollection GetBrokenUnhandledRules(string[] handledFields)
        {
            BusinessRuleCollection brokenUnhandledRules = new BusinessRuleCollection();
            foreach (BusinessRule rule in this.list)
            {
                if (rule.IsObeyed)
                    continue;

                bool isHandled = false;
                foreach (string field in handledFields)
                {
                    if (rule.AffectsField(field))
                    {
                        isHandled = true;
                        break;
                    }
                }

                if (!isHandled)
                    brokenUnhandledRules.Add(rule);
            }

            return brokenUnhandledRules;
        }

        #endregion

        #region IEnumerable.

        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.list.GetEnumerator();
        }

        #endregion

        #region ToString.

        /// <summary>
        /// Concatenates descriptions of all rules in the collection and converts them to <b>string</b>.
        /// </summary>
        /// <returns>Descriptions of all rules.</returns>
        public override string ToString()
        {
            return ToString(null);
        }

        /// <summary>
        /// Concatenates descriptions of all rules in the collection and converts them to <b>string</b>.
        /// </summary>
        /// <param name="formatter">The <b>IMessageFormatter</b> that formattes the values contained in the descriptions of the rules.</param>
        /// <returns>Descriptions of all rules.</returns>
        public string ToString(IMessageFormatter formatter)
        {
            if (this.Count == 0)
                return "";

            FormattedMessage msg;
            if (formatter == null)
                msg = new FormattedMessage("");
            else
                msg = new FormattedMessage("", formatter);

            for (int i = 0; i < this.Count; i++)
            {
                msg += this[i].Description + " ";
            }

            return msg.ToString();
        }

        #endregion
    }
}